{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<center>\n",
    "<img src=\"../../img/ods_stickers.jpg\">\n",
    "## Открытый курс по машинному обучению\n",
    "</center>\n",
    "Автор материала: программист-исследователь Mail.ru Group, старший преподаватель Факультета Компьютерных Наук ВШЭ Юрий Кашницкий. Материал распространяется на условиях лицензии [Creative Commons CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/). Можно использовать в любых целях (редактировать, поправлять и брать за основу), кроме коммерческих, но с обязательным упоминанием автора материала."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# <center>Тема 4. Линейные модели классификации и регрессии\n",
    "## <center>Часть 5. Кривые валидации и обучения"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from __future__ import division, print_function\n",
    "\n",
    "# отключим всякие предупреждения Anaconda\n",
    "import warnings\n",
    "\n",
    "warnings.filterwarnings(\"ignore\")\n",
    "%matplotlib inline\n",
    "import numpy as np\n",
    "import pandas as pd\n",
    "import seaborn as sns\n",
    "from matplotlib import pyplot as plt\n",
    "from sklearn.linear_model import LogisticRegression, LogisticRegressionCV, SGDClassifier\n",
    "from sklearn.model_selection import validation_curve\n",
    "from sklearn.pipeline import Pipeline\n",
    "from sklearn.preprocessing import PolynomialFeatures, StandardScaler"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Мы уже получили представление о проверке модели, кросс-валидации и регуляризации.\n",
    "Теперь рассмотрим главный вопрос:\n",
    "\n",
    "**Если качество модели нас не устраивает, что делать?**\n",
    "\n",
    "- Сделать модель сложнее или упростить?\n",
    "- Добавить больше признаков?\n",
    "- Или нам просто нужно больше данных для обучения?\n",
    "\n",
    "Ответы на данные вопросы не всегда лежат на поверхности.  В частности, иногда использование более сложной модели приведет к ухудшению показателей. Либо добавление наблюдений не приведет к ощутимым изменениям. Способность принять правильное решение и выбрать правильный способ улучшения модели, собственно говоря, и отличает хорошего специалиста от плохого."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Будем работать со знакомыми данными по оттоку клиентов телеком-оператора. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data = pd.read_csv(\"../../data/telecom_churn.csv\").drop(\"State\", axis=1)\n",
    "data[\"International plan\"] = data[\"International plan\"].map({\"Yes\": 1, \"No\": 0})\n",
    "data[\"Voice mail plan\"] = data[\"Voice mail plan\"].map({\"Yes\": 1, \"No\": 0})\n",
    "\n",
    "y = data[\"Churn\"].astype(\"int\").values\n",
    "X = data.drop(\"Churn\", axis=1).values"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Логистическую регрессию будем обучать стохастическим градиентным спуском. Пока объясним это тем, что так быстрее, но далее в программе у нас отдельная статья про это дело.**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "alphas = np.logspace(-2, 0, 20)\n",
    "sgd_logit = SGDClassifier(loss=\"log\", n_jobs=-1, random_state=17)\n",
    "logit_pipe = Pipeline(\n",
    "    [\n",
    "        (\"scaler\", StandardScaler()),\n",
    "        (\"poly\", PolynomialFeatures(degree=2)),\n",
    "        (\"sgd_logit\", sgd_logit),\n",
    "    ]\n",
    ")\n",
    "val_train, val_test = validation_curve(\n",
    "    logit_pipe, X, y, \"sgd_logit__alpha\", alphas, cv=5, scoring=\"roc_auc\"\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Построим валидационные кривые, показывающие, как качество (ROC AUC) на обучающей и проверочной выборке меняется с изменением параметра регуляризации.**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def plot_with_err(x, data, **kwargs):\n",
    "    mu, std = data.mean(1), data.std(1)\n",
    "    lines = plt.plot(x, mu, \"-\", **kwargs)\n",
    "    plt.fill_between(\n",
    "        x,\n",
    "        mu - std,\n",
    "        mu + std,\n",
    "        edgecolor=\"none\",\n",
    "        facecolor=lines[0].get_color(),\n",
    "        alpha=0.2,\n",
    "    )\n",
    "\n",
    "\n",
    "plot_with_err(alphas, val_train, label=\"training scores\")\n",
    "plot_with_err(alphas, val_test, label=\"validation scores\")\n",
    "plt.xlabel(r\"$\\alpha$\")\n",
    "plt.ylabel(\"ROC AUC\")\n",
    "plt.legend();"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Тенденция видна сразу, и она очень часто встречается.\n",
    "\n",
    "1. Для простых моделей тренировочная и валидационная ошибка находятся где-то рядом, и они велики. Это говорит о том, что модель **недообучилась**: то есть она не имеет достаточное кол-во параметров.\n",
    "\n",
    "2. Для сильно усложненных моделей тренировочная и валидационная ошибки значительно отличаются. Это можно объяснить **переобучением**: когда параметров слишком много либо не хватает регуляризации, алгоритм может \"отвлекаться\" на шум в данных и упускать основной тренд.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Сколько нужно данных?\n",
    "\n",
    "Известно, что чем больше данных использует модель, тем лучше. Но как нам понять в конкретной ситуации, помогут ли новые данные? Скажем, целесообразно ли нам потратить \\$ N на труд асессоров, чтобы увеличить выборку вдвое?\n",
    "\n",
    "Поскольку новых данных пока может и не быть, разумно поварьировать размер имеющейся обучающей выборки и посмотреть, как качество решения задачи зависит от объема данных, на которм мы обучали модель. Так получаются **кривые обучения** (**learning curves**).\n",
    "\n",
    "Идея простая: мы отображаем ошибку как функцию от количества примеров, используемых для обучения. При этом параметры модели фиксируются заранее."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from sklearn.model_selection import learning_curve\n",
    "\n",
    "\n",
    "def plot_learning_curve(degree=2, alpha=0.01):\n",
    "    train_sizes = np.linspace(0.05, 1, 20)\n",
    "    logit_pipe = Pipeline(\n",
    "        [\n",
    "            (\"scaler\", StandardScaler()),\n",
    "            (\"poly\", PolynomialFeatures(degree=degree)),\n",
    "            (\"sgd_logit\", SGDClassifier(n_jobs=-1, random_state=17, alpha=alpha)),\n",
    "        ]\n",
    "    )\n",
    "    N_train, val_train, val_test = learning_curve(\n",
    "        logit_pipe, X, y, train_sizes=train_sizes, cv=5, scoring=\"roc_auc\"\n",
    "    )\n",
    "    plot_with_err(N_train, val_train, label=\"training scores\")\n",
    "    plot_with_err(N_train, val_test, label=\"validation scores\")\n",
    "    plt.xlabel(\"Training Set Size\")\n",
    "    plt.ylabel(\"AUC\")\n",
    "    plt.legend()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Давайте посмотрим, что мы получим для линейной модели. Коэффициент регуляризации выставим большим."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plot_learning_curve(degree=2, alpha=10)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Типичная ситуация: для небольшого объема данных ошибки на обучающей выборке и в процессе кросс-валидации довольно сильно отличаются, что указывает на переобучение.  Для той же модели, но с большим объемом данных ошибки \"сходятся\", что указывается на недообучение.\n",
    "\n",
    "Если добавить еще данные, ошибка на обучающей выборке не будет расти, но с другой стороны, ошибка на тестовых данных не будет уменьшаться. \n",
    "\n",
    "Получается, ошибки \"сошлись\", и добавление новых данных не поможет. Собственно, это случай – самый интересный для бизнеса. Возможна ситуация, когда мы увеличиваем выборку в 10 раз. Но если не менять сложность модели, это может и не помочь. То есть стратегия \"настроил один раз – дальше использую 10 раз\" может и не работать. \n",
    "\n",
    "Что будет, если изменить коэффициент регуляризации?\n",
    "Видим хорошую тенденцию – кривые постепенно сходятся, и если дальше двигаться направо (добавлять в модель данные), можно еще повысить качество на валидации. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plot_learning_curve(degree=2, alpha=0.05)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "А если усложнить ещё больше?\n",
    "\n",
    "Проявляется переобучение - AUC падает как на обучении, так и на валидации."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plot_learning_curve(degree=2, alpha=1e-4)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Строя подобные кривые, можно понять, в какую сторону двигаться, и как правильно настроить сложность модели на новых данных."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Выводы\n",
    "\n",
    "- Ошибка на обучающей выборке сама по себе ничего не говорит о качестве модели\n",
    "- Кросс-валидационная ошибка показывает, насколько хорошо модель подстраивается под данные (имеющийся тренд в данных), сохраняя при этом способность обобщения на новые данные\n",
    "- **Валидационная кривая** представляют собой график, показывающий результат на тренировочной и валидационной выборке в зависимости от **сложности модели**:\n",
    "  + если две кривые распологаются близко, и обе ошибки велики, -  это признак *недообучения*\n",
    "  + если две кривые далеко друг от друга, - это показатель *переобучения*\n",
    "- **Кривая обучения**  - это график, показывающий результаты на валидации и тренировочной подвыборке в зависимости от количества наблюдений. \n",
    "  + если кривые сошлись друг к другу, добавление новых данных не поможет – надо менять сложность модели \n",
    "  + если кривые еще не сошлись, добавление новых данных может улучшить результат."
   ]
  }
 ],
 "metadata": {
  "anaconda-cloud": {},
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.1"
  },
  "name": "lesson7_part5_overfitting_validation.ipynb"
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
